package uk.ac.sanger.dasregistry.timer;

import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import java.util.logging.Logger;
import org.biojava.bio.program.das.dasalignment.DASException;
import org.biojava.dasobert.das.Capabilities;
import org.biojava.dasobert.das2.Das2Source;
import org.biojava.dasobert.das2.DasSourceConverter;
import org.biojava.dasobert.das2.io.DasSourceReaderImpl;
import org.biojava.dasobert.dasregistry.Das1Source;
import org.biojava.dasobert.dasregistry.Das1Validator;
import org.biojava.dasobert.dasregistry.DasCoordinateSystem;
import org.biojava.dasobert.dasregistry.DasSource;
import org.biojava.services.das.dao.DasServer;
import org.biojava.services.das.dao.DasServerDAO;
import org.biojava.services.das.dao.ServerManager;
import org.biojava.services.das.dao.DasSourceManager;
import org.biojava.services.das.dao.RegistryUser;
import org.biojava.services.das.registry.ConfigBean;
import org.biojava.services.das.registry.DasRegistry;
import org.biojava.services.das.registry.DasRegistrySql;
import org.biojava.services.das.registry.RegistryException;
import org.biojava.services.das.registry.UserManager;
import org.biojava.services.das2.Das2Mirror;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Mirroring {

	public static Logger logger = Logger
			.getLogger("uk.ac.sanger.dasregistry.timer.Mirroring");

	int contactParentFrequency;

	
	DataSource dataSource;

	DasSourceManager dasSourceManager;
	ServerManager serverManager;
	public DasSourceManager getDasSourceManager() {
		return dasSourceManager;
	}

	public void setDasSourceManager(DasSourceManager dasSourceManager) {
		this.dasSourceManager = dasSourceManager;
	}

	public ServerManager getServerManager() {
		return serverManager;
	}

	public void setServerManager(ServerManager serverManager) {
		this.serverManager = serverManager;
	}

	public UserManager getUserManager() {
		return userManager;
	}

	public void setUserManager(UserManager userManager) {
		this.userManager = userManager;
	}

	UserManager userManager;

	public static final String VERSION_REGEX = "([a-zA-Z_0-9]*?)([0-9\\\\.]*+)?$";

	static Pattern versionPattern = Pattern.compile(VERSION_REGEX);
	DasRegistrySql registry;
	
	public void setRegistry(DasRegistrySql registry) {
		this.registry = registry;
	}

	public Mirroring() {
		//this.getBeans();
		
	}
	
	protected void getBeans(){
		//config = (ConfigBean) context.getBean("configBean");
//		dasSourceManager = new DasSourceManager();
//		BeanFactory ctx=(new ClassPathXmlApplicationContext("SpringDAO.xml"));
//		registry=(DasRegistrySql)ctx.getBean("registryBean");
//		dasServerManager=(DasServerDAO)ctx.getBean("dasServerDAO");
//		userManager=new UserManager();
		
	}

	public void mirrorAll(boolean ignoreAdminChecks){
		List<DasServer> mirrors=null;
		try {
			mirrors = registry.listServers();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		mirrorServers(mirrors, ignoreAdminChecks);
	}

	/**
	 * for each server see what is in its sources document and make sure we only have these in the registry
	 * set active to false for those no in the sources doc
	 * @param servers
	 * @param ignoreAdminChecks
	 */
	private void mirrorServers(List<DasServer> servers, boolean ignoreAdminChecks) {
		// System.out.println("mirroring service called...");
		logger.info("mirroring service called ..");
		
		// add to this list the a list of Das2Mirror objects from the database
		// where users can now add them
		System.out.println("found " + servers.size() + " sites to be mirrored");
		Connection conn = null;
		
			try {
				conn = dataSource.getConnection();
				boolean doMirror = dasSourceManager.doAdminChecks(conn, "lastMirror",
						"mirrorStarted", "mirroring");
				// boolean doMirror=true;
				if(ignoreAdminChecks)doMirror=true;
				if (!doMirror) {
					logger
							.info(" not running mirroring due to dao admin checks... ");
					return;
				}
				if (doMirror) {
					for(DasServer mirror:servers){
						if(mirror.getUserId()!=0){//ignore if no user added
							mirror(conn, mirror);							
						}
						
					}
					
						
					}
					dasSourceManager.releaseAdminLock(conn, "mirroring", "lastMirror");
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			

		
	}

	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	private Das1Source[] getDas1Sources(DasSource[] sources)
			throws DASException {
		List<Das1Source> das1sources = new ArrayList<Das1Source>();
		for (int i = 0; i < sources.length; i++) {
			DasSource ds = sources[i];

			if (ds instanceof Das2Source) {
				System.out.println("das 2");
				Das2Source d2s = (Das2Source) ds;
				if (d2s.hasDas1Capabilities()) {
					Das1Source d1s = DasSourceConverter.toDas1Source(d2s);
					das1sources.add(d1s);
				}

			} else if (ds instanceof Das1Source) {
				das1sources.add((Das1Source) ds);
			}
		}

		return das1sources.toArray(new Das1Source[das1sources.size()]);

	}

	private void fixCoordSys(DasSource[] sources) throws DASException {

		for (int i = 0; i < sources.length; i++) {
			DasSource ds = sources[i];

			DasCoordinateSystem[] sys = ds.getCoordinateSystem();

			for (int j = 0; j < sys.length; j++) {
				DasCoordinateSystem s = sys[j];
				// System.out.println("cs before " + s + " version:" +
				// s.getVersion());
				String cat = s.getName();
				Matcher m = versionPattern.matcher(cat);

				if (m.matches()) {

					if (m.groupCount() == 2) {

						s.setName(m.group(1));
						s.setVersion(m.group(2));
						// System.out.println("cs after 1 " + s + " " +
						// m.group(1) + " " + m.group(2));
					} else {
						s.setVersion("");
					}

				}
				if (s.getName().equals("VEGA")
						&& s.getCategory().equals("Clone")) {
					s.setName("EMBL");
				}
				// System.out.println("cs after 2 " + s + " version:" +
				// s.getVersion());
				sys[j] = s;
			}
			ds.setCoordinateSystem(sys);

		}

	}

	/**
	 * mirror the sources for the server and store the server_id in the registry table
	 * also set as inactive any sources that are not in the sources document for the server.
	 * @param conn
	 * @param mirror
	 */
	private void mirror(Connection conn, DasServer mirror) {

		try {
			DasSourceReaderImpl reader = new DasSourceReaderImpl();

			URL url=null;
			try {
				url = new URL(mirror.getUrl());
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			// System.out.println("doing mirroring after check!");

			// lets check the sources cmd with our validator first!
			Das1Validator validator = new Das1Validator();
			boolean isValid = validator.validateSourcesCmdShallow(url
					.toString(), false);
			if (isValid) {
				
				
				URL readingUrl=new URL(url+"sources");//need sources command on the end as for validation it puts it on but not here
				System.out.println("reading sources with url:"+readingUrl);
				DasSource[] sources = reader.readDasSource(readingUrl);
				
				Das1Source[] das1Sources = getDas1Sources(sources);

				/**
				 * the way ensembl describes the coord sysses is wrong this
				 * needs to be fixed ...
				 */
				// fixCoordSys(das1Sources);
				if (das1Sources.length < 1) {
					logger
							.info(" mirror server at "
									+ mirror.getUrl()
									+ " did return only "
									+ das1Sources.length
									+ " servers. Something seems to be wrong, not triggering mirror");
					return;
				}

				for (int i = 0; i < das1Sources.length; i++) {
				//for (int i = 0; i < 10; i++) {

					Das1Source ds1 = das1Sources[i];
					// if source has a server stored in the server table we need
					// to associate the ds with the server and therefor open_id
					// so lets add a field of server_id to the register table

					String sourcesUrl = Das1Validator
							.removeDataSourceNameFromUrl(ds1.getUrl());
					System.out.println("sources url in mirror=" + sourcesUrl);
					
					if (!isKnownSource(conn, ds1)) {
						logger
								.info("is not a know source so running mirror source!");
						mirrorSource(conn, mirror, ds1, mirror.getServerId());
					} else {

						// lets update all the mirrored data sources so we have
						// the most up to date data

						logger
								.info("updating data source:"
										+ ds1.getNickname());
						try {
							updateSource(conn, ds1, mirror.getServerId());

						} catch (SQLException se) {
							System.err
									.println("an error occured during updating of DAS source "
											+ ds1);
							se.printStackTrace();

						}
					}
				
				//}
				}
			} else {
				logger
						.info("sources document was not valid therefor not mirroring validation messsge="
								+ validator.getValidationMessage());

			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private void updateSource(Connection conn, Das1Source ds, int serverId)
			throws SQLException {
		// get the auto id for this DAS source...

		int auto_id = dasSourceManager.getAutoIdForURL(ds.getUrl());
		System.out.println("got auto_id " + auto_id + " for DAS source "
				+ ds.getNickname() + " " + ds.getId());
		if (auto_id == DasSourceManager.UNKNOWN_AUTO_ID) {
			// most likely the URL has been modified!
			// get the auto_id by the prefix ...
			System.out
					.println("trying to get auto_id from prefix for das source with ID "
							+ ds.getId());
			auto_id = dasSourceManager.getAutoIdForPrefix(conn, ds.getId());
		}
		//sets as active if known
		dasSourceManager.updateInDb(conn, auto_id, ds, null, serverId);
		//need to add the owner of the server to all this specific data source
		addOwner(conn, ds);

		
	}

	private boolean isKnownSource(Connection conn, Das1Source ds) {

		// check if a server with that URL is in the database, if yes
		// it is "known" because the url is a unique way to describe a DAS/1
		// server

		if (!dasSourceManager.isInDatabase(conn, ds.getUrl())) {
			logger
					.info("could not find URL in database, now checking for prefix "
							+ ds.getUrl() + " " + ds.getId());
			if (!dasSourceManager.isKnownPrefix(conn, ds.getId())) {
				return false;
			}
		}
		return true;

	}

	/**
	 * check if a DAS source has a new description, coordinate system or
	 * capability
	 * 
	 * @param conn
	 * @param dataSource
	 * @return true if something was changed
	 */
	private boolean shouldUpdateSource(Connection conn, Das1Source dsNew)
			throws SQLException {
		logger.fine("in shouldUpdateSource method");
		String prefix = null;
		try {
			prefix = DasRegistrySql.getPrefix(dsNew.getId());
		} catch (RegistryException e) {
			e.printStackTrace();
		}
		String url = dsNew.getUrl();

		DasSource dsOld = null;
		try {
			dsOld = dasSourceManager.getDasSourceByUrl(conn, url, prefix);

		} catch (Exception e) {
			// if the URL has been modified this will not work ...
			e.printStackTrace();
		}

		if (dsOld == null)
			return true;

		if (!dsNew.equalsExact(dsOld)) {
			return true;
		}

		return false;

	}

	private void checkCapabilities(Das1Source ds) {

		List<String> caps = ds.getCapabilities();

		List<String> checkedCaps = new ArrayList<String>();

		for (int i = 0; i < caps.size(); i++) {
			String cap = caps.get(i);
			if (!(cap.equals(Capabilities.STYLESHEET))) {
				checkedCaps.add(cap);
			}
		}

		String[] newCaps = checkedCaps.toArray(new String[checkedCaps.size()]);
		ds.setCapabilities(newCaps);

	}

	private void mirrorSource(Connection conn, DasServer mirror,
			Das1Source ds, int serverId) throws SQLException {
		logger.info("mirroring das source:" + ds.getId());

		// Map the coordinate system to the registry
		// if it does not exist, create it

		// this is a mirror so all sources are non-local

		checkCapabilities(ds);

		DasCoordinateSystem[] css = ds.getCoordinateSystem();

		for (int i = 0; i < css.length; i++) {

			DasCoordinateSystem cs = css[i];
			logger.fine("checking coordinate system");

			if (!dasSourceManager.isKnownCoordinateSystem(conn, cs)) {

				logger.info("unknown coordinate system " + cs);
				// dont create new coordinate systems anymore here

			}
			// now store the das source
			dasSourceManager.storeInDb(conn, ds, null, serverId);
			addOwner(conn, ds);


		}

	}

	private void addOwner(Connection conn, Das1Source ds) throws SQLException {
		String serverUrl=Das1Validator.removeDataSourceNameFromUrl(ds.getUrl());
		
		int auto_id = dasSourceManager.getAutoIdForURL(ds.getUrl());
		System.out.println("got auto_id " + auto_id + " for DAS source "
				+ ds.getNickname() + " " + ds.getId());
		if (auto_id == DasSourceManager.UNKNOWN_AUTO_ID) {
			// most likely the URL has been modified!
			// get the auto_id by the prefix ...
			System.out
					.println("trying to get auto_id from prefix for das source with ID "
							+ ds.getId());
			auto_id = dasSourceManager.getAutoIdForPrefix(conn, ds.getId());
		}
		int user_id=serverManager.getUserIdForServerUrl(serverUrl);
		if(user_id!=0){
		RegistryUser user=userManager.getUserById(user_id);
		userManager.addOwner(user, auto_id, false);
		}
	}

	private void cleanMirror(Connection conn, String prefix)
			throws SQLException {
		System.out.println("cleaning mirrored DAS sources");
		prefix = prefix + "%";
		String SELECT_CACHED_SQL = "SELECT auto_id FROM registry where prefix like ?";
		logger
				.info("running clean query : SELECT auto_id FROM registry where prefix like "
						+ prefix);
		PreparedStatement ps = conn.prepareStatement(SELECT_CACHED_SQL);
		ps.setString(1, prefix);
		ResultSet rs = ps.executeQuery();

		// remove each of the cached sources...
		while (rs.next()) {
			int auto_id = rs.getInt(1);
			System.out.println("removing DAS source " + auto_id);
			try {
				dasSourceManager.removeCachedSource(conn, auto_id);
			} catch (RegistryException e) {
				e.printStackTrace();
			}
		}

	}

	private DasRegistry getRegistry() {
		ApplicationContext context = new ClassPathXmlApplicationContext(
				"config_dasregistry.xml");
		DasRegistry registryClient = (DasRegistry) context
				.getBean("dasRegistryClient");
		return registryClient;
	}

	public void mirrorServer(DasServer newServer) {
		List<DasServer> serverList=new ArrayList();
		serverList.add(newServer);
		this.mirrorServers(serverList, true);
		
	}


}
